Exploiting time-sensitive vulnerabilities
Race condition time-sensitive sobre el reset de contraseña: eludir el session lock de PHP con sesiones distintas hace que dos requests en paralelo compartan el mismo token, apropiándose del reset de carlos.
Target Overview & Vulnerability Summary
El endpoint POST /forgot-password construye una URL de reset con los campos user y token. El backend usa PHP, que por defecto serializa las requests de una misma sesión (session lock), lo que impide el procesamiento concurrente. Enviando las requests con tokens de sesión distintos se elude el lock y dos peticiones en paralelo se ejecutan en la misma ventana, generando el mismo token para ambas. El token compartido permite apropiarse del reset de contraseña del usuario carlos.
| Campo | Valor |
|---|---|
| Severidad | Alta (apropiación de cuenta vía token de reset compartido) |
| Endpoint vulnerable | POST /forgot-password |
| Root cause | Generación de token dependiente del tiempo/estado compartido + eludir el session lock de PHP permite paralelizar requests de la misma operación |
Technical Breakdown & Exploitation Flow
1. Observación inicial
El formulario de reset genera una URL con user y token:
https://0a60006003e927ff81b3253f0060008e.web-security-academy.net/forgot-password?user=wiener&token=4c81477a41e9d91d681e1b557717d2705dcc123d
El token tiene longitud constante, lo que sugiere que es un hash de un dato que contiene estado interno (RNG, contador o timestamp): la aleatoriedad aparente podría colapsar cuando dos generaciones ocurren en la misma unidad de tiempo. El servidor verifica que el campo user esté relacionado con el token.
2. Detección del session lock de PHP
En un grupo de Repeater se envían dos requests en paralelo de:
POST /forgot-password HTTP/2
Host: 0a60006003e927ff81b3253f0060008e.web-security-academy.net
csrf=zROddqG4LdBZfrANjAVwJB4G08WhOrX1&username=wiener
La cookie phpsessionid confirma backend PHP. Al revisar el cliente de correo, los dos enlaces tienen tokens distintos y las requests muestran delay entre sí: se ejecutan secuencialmente. PHP serializa las requests por sesión (session locking): mientras una request de la sesión está en procesamiento, las demás esperan, cerrándose la ventana de carrera.
3. Bypass del session lock
Para paralelizar, cada request debe usar una sesión distinta:
- En el navegador, eliminar la cookie
phpsessionidy recargar para obtener una nueva sesión. - Copiar la nueva cookie y su token CSRF (leer el valor del formulario en la página).
- Enviar las dos requests en paralelo, cada una con su propio
phpsessionid.
Esta vez el delay entre requests cae a ~1 ms: las dos se procesan de forma concurrente. En el cliente de correo, ambos enlaces tienen el mismo token:
https://0a60006003e927ff81b3253f0060008e.web-security-academy.net/forgot-password?user=wiener&token=1f6a8be78dc785a4f99fb57f51f6ffd3fba03e3a
https://0a60006003e927ff81b3253f0060008e.web-security-academy.net/forgot-password?user=wiener&token=1f6a8be78dc785a4f99fb57f51f6ffd3fba03e3a
El token idéntico revela una componente temporal: las dos generaciones ocurrieron en la misma ventana de tiempo y produjeron el mismo valor.
4. Apropiación del reset de carlos
Con el paralelismo confirmado, se repite la ráfaga solicitando el reset para dos usuarios: uno propio (cuyo correo controlamos) y carlos. La request de carlos se procesa en la misma ventana y comparte el token de la request propia:
- Enviar en paralelo dos
POST /forgot-password: una conusername=wiener, otra conusername=carlos, cada una con su sesión. - No se recibirán dos enlaces: el segundo correo se exfiltra al buzón de
carlos. Lo que se obtiene en el buzón propio es el enlace con el token de la operación compartida. - Reemplazar el campo
userporcarlosconservando el token recibido:
/forgot-password?user=carlos&token=d7c8af5de94d1c60857cebca3fabc62a026e572a
- Si la verificación
user ↔ tokenes válida paracarlos(el token corresponde a la generación compartida), se accede al formulario de reset y se cambia la contraseña decarlos.
Nota de fiabilidad: el cruce de tokens depende de la coincidencia temporal; puede requerir varios intentos hasta que el token de la request de carlos quede ligado al enlace propio.
Root Cause Analysis
Dos condiciones se combinan:
- Generación de token time-sensitive: el token incorpora estado temporal de granularidad gruesa; dos generaciones dentro de la misma unidad de tiempo producen el mismo valor.
- Session lock eludible: la serialización de PHP por sesión es la única barrera de concurrencia; usando sesiones distintas, la misma operación se ejecuta en paralelo y la ventana de tiempo se comparte, generando tokens colisionantes.
El resultado es un Broken Access Control: el token emitido para un flujo legítimo del atacante es reutilizable para el flujo de otro usuario porque la verificación user ↔ token no es lo bastante estricta (el token no está ligado criptográficamente al user en el momento de emitirse).
Secure Remediation
- Generar tokens con CSPRNG de 256 bits por operación, sin componentes temporales predecibles:
$token = bin2hex(random_bytes(32));
- Vincular el token al usuario y a la operación de forma explícita en la verificación: comprobar
userytokencomo par, y marcar el token como usado de forma atómica tras el reset.
$row = db_fetch("SELECT * FROM password_resets
WHERE token = :token AND username = :user AND used = FALSE
FOR UPDATE");
if (!$row) abort(403);
db_execute("UPDATE password_resets SET used = TRUE WHERE id = :id");
- Persistir el token por operación (fila con
username,token,expires_at,used) en lugar de depender de estado efímero, e invalidar tokens previos al crear uno nuevo dentro de la misma transacción.